﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace MappitLib {
    public partial class TilemapEditor : UserControl {
        //***********************************************************
        #region Defaults
        static Color DefaultGridColor = Color.LightGray;
        #endregion
        //***********************************************************
        #region View properties
        public bool HasVisibleGrid = true;
        #endregion
        //***********************************************************
        #region Members
        private List<Layer> layers_ = new List<Layer>();
        private Layer active_layer_;
        #endregion
        //***********************************************************
        #region Properties
        public List<Layer> Layers {
            get { return layers_; }
            private set { layers_ = value; }
        }
        public Tileset Tileset {
            get { return Tilemap.TileSet; }
            set {
                foreach (Layer l in layers_) {
                    l.Tilemap.TileSet = value;
                }
                AutoSizePanel();
                imagePanel.Refresh();
            }
        }
        public Tilemap Tilemap {
            get {
                return active_layer_.Tilemap;
            }
        }
        public Layer ActiveLayer {
            get {
                return active_layer_;
            }
            set {
                if (Layers.Contains(value)) {
                    active_layer_ = value;
                }
            }
        }
        public string Filename {
            get { return Tilemap.Filename; }
            set {
                Tilemap.Filename = value;
                Name = value;
            }
        }
        #endregion
        //***********************************************************
        #region Methods
        public TilemapEditor() {
            InitializeComponent();
            active_layer_ = AddLayer();
        }
        public Layer AddLayer() {
            Layer new_layer = new Layer();
            Layers.Add(new_layer);
            new_layer.Tilemap.TileSet = Tileset;
            return Layers.Last();
        }
        public void RemoveLayer() {
            // get index of active layer
            int active_index = Layers.IndexOf(active_layer_);
            // remove the active layer
            Layers.Remove(active_layer_);
            // if no layers are left, create a new empty one
            if (Layers.Count == 0) {
                AddLayer();
            }
            // if the active layer is still valid...
            if (active_index < Layers.Count()) {
                active_layer_ = Layers[active_index];
            } // otherwise...
            else {
                active_layer_ = Layers.Last();
            }
        }
        public void ToggleVisibleGrid() {
            HasVisibleGrid = !HasVisibleGrid;
        }
        private void DrawTilemap(PaintEventArgs e) {
            foreach (Layer l in Layers) {
                if (l.Visible) {
                    DrawLayer(e, l);
                }
                if (HasVisibleGrid) {
                    DrawGrid(e, l);
                }
            }
        }
        private void DrawGrid(PaintEventArgs e, Layer l) {
            for (int x = 0; x < l.Tilemap.Width; ++x) {
                for (int y = 0; y < l.Tilemap.Height; ++y) {
                    // get rectangular area where tile will be drawn
                    Rectangle tile_rect = GetTileRect(x, y);
                    // determine if the tile is in the clip region
                    if (tile_rect.IntersectsWith(e.ClipRectangle)) {
                        using (Pen p = new Pen(DefaultGridColor)) {
                            e.Graphics.DrawRectangle(p, tile_rect);
                        }
                    }
                }
            }
        }
        private void DrawLayer(PaintEventArgs e, Layer l) {
            for (int x = 0; x < l.Tilemap.Width; ++x) {
                for (int y = 0; y < l.Tilemap.Height; ++y) {
                    Tile tile = l.Tilemap.GetTile(x, y);
                    DrawTile(e, x, y, tile);
                }
            }
        }
        private void DrawTile(PaintEventArgs e, int x, int y, Tile tile) {
            // get rectangular area where tile will be drawn
            Rectangle tile_rect = GetTileRect(x, y);
            // determine if the tile is in the clip region
            if (tile_rect.IntersectsWith(e.ClipRectangle)) {
                if (tile != null && Tileset.Bitmap != null) {
                    // draw tile bitmap from tileset
                    e.Graphics.DrawImage(Tileset.Bitmap,
                        x * Tileset.TileWidth,
                        y * Tileset.TileHeight,
                        Tileset.GetTileRect(tile.Location),
                        GraphicsUnit.Pixel);
                }
            }
        }

        public void AutoSizePanel() {
            if (Tileset != null) {
                Size panel_size = imagePanel.Size;
                panel_size.Width = Tilemap.Width * Tileset.TileWidth;
                panel_size.Height = Tilemap.Height * Tileset.TileHeight;
                imagePanel.Size = panel_size;
            } else {
                Size panel_size = new Size();
                imagePanel.Size = panel_size;
            }
        }
        public Rectangle GetTileRect(int x, int y) {
            return GetTileRect(new Point(x, y));
        }
        public Rectangle GetTileRect(Point tile_coord) {
            if (Tileset == null) {
                return new Rectangle();
            }
            if (tile_coord == Tilemap.InvalidCoord) {
                return new Rectangle();
            } else {
                return new Rectangle(tile_coord.X * Tileset.TileWidth,
                tile_coord.Y * Tileset.TileHeight,
                Tileset.TileWidth,
                Tileset.TileHeight);
            }
        }
        public void SetTile(Tile tile, Point tile_coord) {
            // if the tile drop location is out-of-bounds, do nothing
            if (tile_coord == Tilemap.InvalidCoord) {
                return;
            }
            // set the tile
            Tilemap.SetTile(tile, tile_coord);
            // invalidate the painted area
            imagePanel.Invalidate(GetTileRect(tile_coord));
        }
        public Tile GetTile(Point tile_coord) {
            return Tilemap.GetTile(tile_coord.X, tile_coord.Y);
        }
        public Point GetTileCoord(Point pixel_coord) {
            // if no tileset has been assigned, return an invalid tile coord (-1, -1)
            if (Tilemap.TileSet == null) {
                return Tilemap.InvalidCoord;
            }
            // determine tile size
            Size tile_size = new Size(Tilemap.TileSet.TileWidth, Tilemap.TileSet.TileHeight);
            // determine tile coordinates
            Point tile_coord = new Point(
                pixel_coord.X / tile_size.Width,
                pixel_coord.Y / tile_size.Height);
            // bounds check
            if (tile_coord.X > Tilemap.Width || tile_coord.Y > Tilemap.Height ||
                tile_coord.X < 0 || tile_coord.Y < 0) {
                tile_coord = Tilemap.InvalidCoord;
            }
            // return the coord
            return tile_coord;
        }
        #endregion
        //***********************************************************
        #region Custom Events
        public event EventHandler<TileDropEventArg> RaiseTileDropEvent;
        #endregion
        //***********************************************************
        #region Event Handlers
        private void imagePanel_Paint(object sender, PaintEventArgs e) {
            if (DesignMode) {
                return;
            }
            // set image panel size to fit all tiles
            AutoSizePanel();
            // draw the tile map
            DrawTilemap(e);
        }
        private void imagePanel_MouseClick(object sender, MouseEventArgs e) {
            OnRaiseTileDropEvent(new TileDropEventArg(GetTileCoord(e.Location)));
        }

        protected virtual void OnRaiseTileDropEvent(TileDropEventArg e) {
            // Make a temporary copy of the event to avoid possibility of
            // a race condition if the last subscriber unsubscribes
            // immediately after the null check and before the event is raised.
            EventHandler<TileDropEventArg> handler = RaiseTileDropEvent;

            // Event will be null if there are no subscribers
            if (handler != null) {
                // Use the () operator to raise the event.
                handler(this, e);
                // invalidate the tile region
                imagePanel.Invalidate(GetTileRect(e.DropLocation));
            }
        }
        #endregion

        private void statusStrip1_Paint(object sender, PaintEventArgs e) {

        }
    }

    public class TileDropEventArg : EventArgs {
        private Point drop_location_ = Tilemap.InvalidCoord;
        public Point DropLocation { get; set; }
        public TileDropEventArg(Point drop_location) {
            DropLocation = drop_location;
        }
    }

}
